﻿#include "QCefWidget.h"
#include "QCefUtil.h"

//////////////////////////////////////////////////////////////////////////
//
QCefWidget::QCefWidget(QWidget * parent, Qt::WindowFlags flags)
    : ExtendWidget(parent, flags)
    , m_browserState(BrowserState::bsNone)
    , m_iLoadFirstUrl(0)
    , m_layout(nullptr)
    , m_window(nullptr)
    , m_container(nullptr)
    , m_iReqFilter(0)
    , m_iReqCompleteFilter(0)
    , m_iResFilter(0)
    , m_iSignalDownProgress(0)
{
    QCEF_DEBUG();

    //
    connect(this, &QCefWidget::pageAfterCreated, this, &QCefWidget::onPageAfterCreated);

    //在构造函数中调用虚函数时，会调用当前类的函数
    //在子类中继承继承本构造函数，进行调用
    //createContainer();
    //createClient();
}

QCefWidget::QCefWidget(const QString & strUrl, QWidget * parent, Qt::WindowFlags flags)
    : ExtendWidget(parent, flags)
    , m_browserState(BrowserState::bsNone)
    , m_strFirstUrl(strUrl)
    , m_iLoadFirstUrl(0)
    , m_layout(nullptr)
    , m_window(nullptr)
    , m_container(nullptr)
    , m_iReqFilter(0)
    , m_iReqCompleteFilter(0)
    , m_iResFilter(0)
    , m_iSignalDownProgress(0)
{
    QCEF_DEBUG();

    //
    connect(this, &QCefWidget::pageAfterCreated, this, &QCefWidget::onPageAfterCreated);

    //在构造函数中调用虚函数时，会调用当前类的函数
    //createContainer();
    //createClient();
}

QCefWidget::~QCefWidget()
{
    QCEF_DEBUG();

    //不要再析构函数中调用虚函数
    QCefWidget::finiCefCtrl();
}


int QCefWidget::finiCefCtrl()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        //delete m_browser;
        m_browser = nullptr;
    }

    if (m_client != nullptr)
    {
        m_client->finiClient();

        //delete m_client;
        m_client = nullptr;
    }

    if (m_delegate != nullptr)
    {
        m_delegate->finiDelegate();

        //delete m_delegate;
        m_delegate = nullptr;
    }

    return 0;
}

int QCefWidget::createContainer()
{
    QCEF_DEBUG();

#ifndef QT_CEF_OSR // is QT_CEF_OSR
    //NCW模式下，CEF创建的浏览器窗口的父窗口，如果直接是QCefWidget的话，会出现输入焦点无法切换的问题。
    //需要用container包住CEF创建的浏览器窗口。
    if (m_window == nullptr)
    {
        m_window = new QWindow(this->windowHandle());
        m_layout = new QVBoxLayout(this);
        m_layout->setContentsMargins(0,0,0,0);
        m_layout->setSpacing(0);
        m_container = QWidget::createWindowContainer(m_window, this);
        m_layout->addWidget(m_container);
        this->setLayout(m_layout);

        return 0;
    }
#endif

    return 1;
}

int QCefWidget::createClient()
{
    QCEF_DEBUG();

    //
    if (m_delegate == nullptr)
    {
        m_delegate = new QCefDelegate(this);
        m_client = new QCefClient(QCefBrowserApp::instance());

        m_delegate->setOperator(this);
        m_client->setDelegate(m_delegate);

        return 0;
    }

    return 1;
}

int QCefWidget::createBrowser(const QString & strUrl, const QSize & size, DWORD style, DWORD ex_style)
{
    QCEF_DEBUG();

    if (m_browserState != BrowserState::bsNone)
    {
        return 1;
    }
    m_browserState = BrowserState::bsCreating;

    //
#ifdef QT_CEF_OSR // is QT_CEF_OSR
    CefWindowInfo windowinfo;
    windowinfo.SetAsWindowless((HWND)this->winId());
#else       //not QT_CEF_OSR
    //...
    QSize wndSize = (size.isEmpty() ? m_container->size() : size);
#if CEF_VERSION_MAJOR <= 89
    RECT rect{0, 0, wndSize.width(), wndSize.height()};
#else   //CEF_VERSION_MAJOR > 89
    CefRect rect{0, 0, wndSize.width(), wndSize.height()};
#endif  //end CEF_VERSION_MAJOR

    CefWindowInfo windowinfo;
    windowinfo.SetAsChild((HWND)m_window->winId(), rect);

    if (style > 0)
    {
        windowinfo.style = style;
    }

    if (ex_style > 0)
    {
        windowinfo.ex_style = ex_style;
    }
#endif      //end QT_CEF_OSR

    //...
    CefString url = strUrl.toStdString();

    windowinfo.style ^= WS_VISIBLE;
    windowinfo.ex_style |= WS_EX_NOACTIVATE;

    return (CefBrowserHost::CreateBrowser(windowinfo, m_client, url, cefBrowserSettings(), nullptr, nullptr) ? 0 : -1);
}

int QCefWidget::browserCreated(CefRefPtr<CefBrowser> browser)
{
    QCEF_DEBUG();

    m_browser = browser;
    m_browserState = BrowserState::bsCreated;

    return 0;
}


void QCefWidget::onPageAfterCreated()
{
    QCEF_DEBUG();

    if (m_iLoadFirstUrl > 0 && m_strFirstUrl.length() > 0)
    {
        loadUrl(m_strFirstUrl);
    }

    //
    resizeBrowser(this->size());
}

void QCefWidget::firstPolishEvent(QEvent * event)
{
    QCEF_DEBUG();

    this->createContainer();
    this->createClient();
    this->createBrowser(m_strFirstUrl, this->size());
}

void QCefWidget::resizeEvent(QResizeEvent * event)
{
    QCEF_DEBUG();

    if (m_browserState == BrowserState::bsCreated)
    {
        this->resizeBrowser(event->size());
    }
    //else if (m_browserState == BrowserState::bsNone)
    //{
    //    this->createContainer();
    //    this->createClient();
    //    this->createBrowser(m_strFirstUrl, event->size());
    //}

    //
    __super::resizeEvent(event);
}


void QCefWidget::closeEvent(QCloseEvent *event)
{
    QCEF_DEBUG();

    this->finiCefCtrl();

    //
    __super::closeEvent(event);
}

QSize QCefWidget::sizeHint() const
{
    QCEF_DEBUG();

    return QSize(300, 300);
}

int QCefWidget::resizeBrowser(const QSize & size)
{
    QCEF_DEBUG();

    if (m_browserState == BrowserState::bsCreated && m_browser != nullptr)
    {
        CefRefPtr<CefBrowserHost> host = m_browser->GetHost();
        if (host != nullptr)
        {
#ifdef QT_CEF_OSR // is QT_CEF_OSR
            host->WasResized();
#else       //not QT_CEF_OSR
            HWND wnd = (HWND)host->GetWindowHandle();
            if (wnd != (HWND)nullptr && wnd != (HWND)-1)
            {
                QSize wndSize = (size.isEmpty() ? m_container->size() : size);

                //::MoveWindow(wnd, 0, 0, wndSize.width(), wndSize.height(), FALSE);
                ::SetWindowPos(wnd, HWND_TOP, 0, 0, wndSize.width(), wndSize.height(), SWP_SHOWWINDOW | SWP_NOACTIVATE);

                return 0;
            }
#endif      //end QT_CEF_OSR
        }
    }

    return -1;
}


WId QCefWidget::getPageWId()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        return (WId)(m_browser->GetHost()->GetWindowHandle());
    }

    return (WId)(nullptr);
}

int QCefWidget::loadRequest(const QString& strUrl, const QString& strMethod, const QString& strPostData, const QStrMultiMap& header)
{
    if (m_browser == nullptr || m_browser->GetMainFrame() == nullptr)
    {
        return -1;
    }

    CefRefPtr<CefRequest> req = CefRequest::Create();

    req->SetURL(QStrToCefStr(strUrl));

    if (strMethod.length() > 0)
    {
        req->SetMethod(QStrToCefStr(strMethod));
    }

    if (strPostData.length() > 0)
    {
        QByteArray data = strPostData.toUtf8();

        CefRefPtr<CefPostDataElement> element = CefPostDataElement::Create();
        element->SetToBytes(data.size(), data.data());

        CefRefPtr<CefPostData> postData = CefPostData::Create();
        postData->AddElement(element);

        req->SetPostData(postData);
    }

    if (header.size() > 0)
    {
        std::multimap<CefString, CefString> hs = QMapToCefMap(header);

        req->SetHeaderMap(hs);
    }

    if (m_browser != nullptr || m_browser->GetMainFrame() != nullptr)
    {
        m_browser->GetMainFrame()->LoadRequest(req);

        return 0;
    }

    return -2;
}

int QCefWidget::load(const QString & strUrl)
{
    QCEF_DEBUG();

    return loadUrl(strUrl);
}

int QCefWidget::load(const QUrl & url)
{
    QCEF_DEBUG();

    return loadUrl(url.url());
}

int QCefWidget::loadUrl(const QString & strUrl)
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_iLoadFirstUrl = 0;

        CefString url = strUrl.toStdString();
        m_browser->GetMainFrame()->LoadURL(url);

        return 0;
    }
    else
    {
        m_strFirstUrl = strUrl;
        m_iLoadFirstUrl = 1;
    }

    return -1;
}

int QCefWidget::loadUrl(const QUrl & url)
{
    QCEF_DEBUG();

    return loadUrl(url.url());
}

QString QCefWidget::getUrl()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        CefRefPtr<CefFrame> mainFrame = m_browser->GetMainFrame();
        if (mainFrame != nullptr)
        {
            CefString url = mainFrame->GetURL();

            return QString::fromStdString(url.ToString());
        }
    }

    return "";
}

int QCefWidget::setUrl(const QString & strUrl)
{
    QCEF_DEBUG();

    return loadUrl(strUrl);
}


bool QCefWidget::canGoBack()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        return m_browser->CanGoBack();
    }

    return false;
}

void QCefWidget::goBack()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_browser->GoBack();
    }
}

bool QCefWidget::canGoForward()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        return m_browser->CanGoForward();
    }

    return false;
}

void QCefWidget::goForward()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_browser->GoForward();
    }
}

bool QCefWidget::isLoading()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        return m_browser->IsLoading();
    }

    return false;
}

void QCefWidget::reload()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_browser->Reload();
    }
}

void QCefWidget::reloadIgnoreCache()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_browser->ReloadIgnoreCache();
    }
}

void QCefWidget::stopLoad()
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_browser->StopLoad();
    }
}

void QCefWidget::setBrowserFocus(bool focus)
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        m_browser->GetHost()->SetFocus(focus);
    }
}

bool QCefWidget::waitPageCreate(qint64 msec)
{
    QCEF_DEBUG();

    QElapsedTimer t;
    t.start();
    while (m_browserState != BrowserState::bsCreated && t.elapsed() < msec)
    {
        QCoreApplication::processEvents();
    }

    return (m_browserState == BrowserState::bsCreated);
}

bool QCefWidget::waitPageCreateSleep(qint64 msec)
{
    QCEF_DEBUG();

    QElapsedTimer t;
    t.start();
    while (m_browserState != BrowserState::bsCreated && t.elapsed() < msec)
    {
        QThread::sleep(10);
    }

    return (m_browserState == BrowserState::bsCreated);
}


bool QCefWidget::toHtml(const std::function<void (const QString &)> & resultCallback, int64 frameId)
{
    QCEF_DEBUG();

    class Visitor : public CefStringVisitor
    {
    public:
        explicit Visitor(const std::function<void (const QString &)> & resultCallback)
            : m_callback(resultCallback)
        {

        }

        virtual void Visit(const CefString & str) override
        {
            QString s = QString::fromStdString(str.ToString());

            (m_callback)(s);
        }

    private:
        const std::function<void (const QString &)> m_callback;
        IMPLEMENT_REFCOUNTING(Visitor);
    };

    if (m_browser != nullptr)
    {
        //CefRefPtr<CefFrame> frame = m_browser->GetMainFrame();
        CefRefPtr<CefFrame> frame;
        if (frameId == 0)
        {
            frame = m_browser->GetMainFrame();
        }
        else
        {
            frame = m_browser->GetFrame(frameId);
            if (frame == nullptr)
            {
                frame = m_browser->GetMainFrame();
            }
        }

        if (frame != nullptr)
        {
            frame->GetSource(new Visitor(resultCallback));

            return true;
        }
    }

    return false;
}

bool QCefWidget::runJavaScript(const QString & strCode, int64 frameId)
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {        
        //CefRefPtr<CefFrame> frame = m_browser->GetMainFrame();
        CefRefPtr<CefFrame> frame;
        if (frameId == 0)
        {
            frame = m_browser->GetMainFrame();
        }
        else
        {
            frame = m_browser->GetFrame(frameId);
            if (frame == nullptr)
            {
                frame = m_browser->GetMainFrame();
            }
        }

        if (frame != nullptr)
        {
            CefString jsCode(strCode.toStdString());

            frame->ExecuteJavaScript(jsCode, frame->GetURL(), 0);

            return true;
        }
    }

    return false;
}

bool QCefWidget::runJavaScriptAsyncResult(int id, const QString & strCode, int64 frameId)
{
    QCEF_DEBUG();

    /*
    * JavaScript code:
    * window.__report_js_result__(id, function() { ... })());
    * 结果通过信号pageReportJavaScriptResult返回
    */

    if (m_browser != nullptr)
    {
        //CefRefPtr<CefFrame> frame = m_browser->GetMainFrame();
        CefRefPtr<CefFrame> frame;
        if (frameId == 0)
        {
            frame = m_browser->GetMainFrame();
        }
        else
        {
            frame = m_browser->GetFrame(frameId);
            if (frame == nullptr)
            {
                frame = m_browser->GetMainFrame();
            }
        }

        if (frame != nullptr)
        {
            std::ostringstream codeWrapper;
            codeWrapper << "window.__report_js_result__(" << id << ",function(){" << strCode.toStdString() << "}());";

            frame->ExecuteJavaScript(codeWrapper.str().c_str(), frame->GetURL(), 0);

            return true;
        }
    }

    return false;
}


bool QCefWidget::runJavaScriptFull(const QString & strCode)
{
    QCEF_DEBUG();

    bool ret = false;

    if (m_browser != nullptr)
    {
        std::vector<int64> ids;
        m_browser->GetFrameIdentifiers(ids);

        for (int i = 0; i < ids.size(); i++)
        {
            ret = runJavaScript(strCode, ids[i]);
        }
    }

    return ret;
}

bool QCefWidget::runJavaScriptAsyncResultFull(int id, const QString & strCode)
{
    QCEF_DEBUG();

    bool ret = false;

    if (m_browser != nullptr)
    {
        std::vector<int64> ids;
        m_browser->GetFrameIdentifiers(ids);

        for (int i = 0; i < ids.size(); i++)
        {
            ret = runJavaScriptAsyncResult(id+i, strCode, ids[i]);
        }
    }

    return ret;
}


bool QCefWidget::findRequestFilter(QString strMethod, QString strUrl, int iResourceType, QString & strKey)
{
    QCEF_DEBUG();

    if (m_iReqFilter <= 0 || m_reqFilterKeys.length() <= 0)
    {
        return false;
    }

    for (QStringList::iterator it = m_reqFilterKeys.begin(); it != m_reqFilterKeys.end(); it++)
    {
        if (strUrl.indexOf(*it) >= 0)
        {
            strKey = *it;

            return true;
        }
    }

    return false;
}

bool QCefWidget::findResponseFilter(QString strMethod, QString strUrl, int iResourceType, QString & strKey)
{
    QCEF_DEBUG();

    if (m_iResFilter <= 0 || m_resFilterKeys.length() <= 0)
    {
        return false;
    }

    for (QStringList::iterator it = m_resFilterKeys.begin(); it != m_resFilterKeys.end(); it++)
    {
        if (strUrl.indexOf(*it) >= 0)
        {
            strKey = *it;

            return true;
        }
    }

    return false;
}


bool QCefWidget::startDownload(const QString& strUrl, const QString & strSaveFilePath)
{
    QCEF_DEBUG();

    if (m_browser != nullptr)
    {
        CefRefPtr<CefBrowserHost> host = m_browser->GetHost();
        if (host != nullptr)
        {
            m_strDownloadUrl = strUrl;
            m_strSaveFilePath = QDir::toNativeSeparators(strSaveFilePath);

            CefString url(strUrl.toStdString());
            host->StartDownload(url);

            return true;
        }
    }

    return false;
}


bool QCefWidget::isDownloadFile(QString strOrgUrl, QString strUrl)
{
    QCEF_DEBUG();

    // strOrgUrl == m_strDownloadUrl
    return true;
}
